{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 多类型文档处理\n",
    "`LangChain`为用户读取数据提供了多种文档`Loader`足以应对绝大部分数据，而各个`Loader`所需参数大同小异，希望大家通过前两部分学习后可以轻松读取自己的数据。\n",
    "## 一、结构化数据\n",
    "结构化数据是指数据由二维表形式按严谨的逻辑存储的数据，主要由数字、文本、日期等离散数据构成。结构化数据不仅便于人们理解和操作还可以被关系数据库读取、处理。csv文件就是典型的结构化数据。  \n",
    "> 想了解pdf与markdown文件如何读取可参考[第一部分数据处理](https://github.com/datawhalechina/llm-universe/blob/main/docs/C3/3.%E6%95%B0%E6%8D%AE%E5%A4%84%E7%90%86.md)内容。\n",
    "* 本节我们选用DataWhale开源课程[joyful-pandas](https://github.com/datawhalechina/joyful-pandas/tree/master)中部分数据演示。\n",
    "### csv文件"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们可以使用`langchain.document_loaders.csv_loader`的`CSVLoader`来读取。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.document_loaders.csv_loader import CSVLoader\n",
    "\n",
    "loader = CSVLoader('./data/Company.csv')\n",
    "csv_data = loader.load()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "读取后返回的类型为`list`，`list`中每个元素为`Document`,`Document`中包含存放某行信息的`page_content`和存放文件相关信息的`metadata`。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "读取后返回的类型为:<class 'list'>\n",
      "每个元素的类型为:<class 'langchain_core.documents.base.Document'>\n",
      "第一个 page_content 中存放的数据为:\n",
      "EmployeeID: 1318\n",
      "birthdate_key: 1/3/1954\n",
      "age: 61\n",
      "city_name: Vancouver\n",
      "department: Executive\n",
      "job_title: CEO\n",
      "gender: M\n",
      "page_content 数据类型为:<class 'str'>\n",
      "第一个 metadata 中存放的数据为:{'source': './data/Company.csv', 'row': 0}\n",
      "metadata 数据类型为:<class 'dict'>\n"
     ]
    }
   ],
   "source": [
    "print(f'读取后返回的类型为:{type(csv_data)}')\n",
    "print(f'每个元素的类型为:{type(csv_data[0])}')\n",
    "print(f'第一个 page_content 中存放的数据为:\\n{csv_data[0].page_content}')\n",
    "print(f'page_content 数据类型为:{type(csv_data[0].page_content)}')\n",
    "print(f'第一个 metadata 中存放的数据为:{csv_data[0].metadata}')\n",
    "print(f'metadata 数据类型为:{type(csv_data[0].metadata)}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`page_content`及`metadata`的数据我们也可以通过字符串和字典方式进行操作。比如为了方便llm理解我们可以把数据进行二次处理。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "字符串处理的结果为: 工号1318的员工于1/3/1954出生，现在61岁，居住城市为Vancouver工作部门为Executive职位是CEO性别为M(M为男性F为女性)。\n",
      "metadata中存放的路径为: ./data/Company.csv\n",
      "metadata中存放的行号为: 0\n"
     ]
    }
   ],
   "source": [
    "print('字符串处理的结果为:',csv_data[0].page_content.replace('\\n', '').replace(' ', '').replace('EmployeeID:', '工号').replace('birthdate_key:', '的员工于').replace('age:', '出生，现在').replace('city_name:', '岁，居住城市为').replace('department:', '工作部门为').replace('job_title:', '职位是').replace('gender:', '性别为') + '(M为男性F为女性)。')\n",
    "print('metadata中存放的路径为:', csv_data[0].metadata['source'])\n",
    "print('metadata中存放的行号为:', csv_data[0].metadata['row'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 二、非结构化数据\n",
    "\n",
    "### 1.PPT文件\n",
    "* 本部分我们选用DataWhale开源课程[sora-tutorial](https://github.com/datawhalechina/sora-tutorial/tree/65415e6e56671d1d52b3a0cf2772a83fc7ad2981)中部分数据演示。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.document_loaders.powerpoint import UnstructuredPowerPointLoader\n",
    "loader = UnstructuredPowerPointLoader('data/AI视频.pptx')\n",
    "ppt_data = loader.load()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "返回的数据为`list`类型，其中`Document`中的`page_content`存放文件中文字内容，同样在`Document`中的`metadata`则存放文件地址`source`。与上文`CSVLoader`的不同是`UnstructuredPowerPointLoader`会将整个PPT内容存到一个`page_content`中，而`CSVLoader`返回的数据是一个`page_content`存放一行的数据。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "读取后返回的类型为:<class 'list'>\n",
      "读取后返回的长度为:1\n",
      "每个元素的类型为:<class 'langchain_core.documents.base.Document'>\n",
      "第一个 page_content 中存放的数据为:\n",
      "Sora原理与实战\n",
      "\n",
      "动手学习AI视频\n",
      "\n",
      "\n",
      "\n",
      "现有AI视频软件盘点\u000b\n",
      "\n",
      "    随着2023年ChatGPT的爆发，让AI的普及率迅速提升，切实的让人感受到AI的给普通人带来的影响，在2023年火了一年的LLM后，视频领域也是也在2024年迅速崛起，前有前辈Runway，后有新生代产品pika，当然也有大名鼎鼎的开源救星SD的当家产品，Stable Video Diffusion，当这几家视频生成\n",
      "page_content 数据类型为:<class 'str'>\n",
      "metadata 中存放的数据为:{'source': 'data/AI视频.pptx'}\n",
      "metadata 数据类型为:<class 'dict'>\n",
      "metadata 存放的 source 为: data/AI视频.pptx\n"
     ]
    }
   ],
   "source": [
    "print(f'读取后返回的类型为:{type(ppt_data)}')\n",
    "print(f'读取后返回的长度为:{len(ppt_data)}')\n",
    "print(f'每个元素的类型为:{type(ppt_data[0])}')\n",
    "print(f'第一个 page_content 中存放的数据为:\\n{ppt_data[0].page_content[:200]}')\n",
    "print(f'page_content 数据类型为:{type(ppt_data[0].page_content)}')\n",
    "print(f'metadata 中存放的数据为:{ppt_data[0].metadata}')\n",
    "print(f'metadata 数据类型为:{type(ppt_data[0].metadata)}')\n",
    "print(f'metadata 存放的 source 为:', str(ppt_data[0].metadata['source']))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们同样可以通过字符串操作处理读取的数据："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "处理后的数据:\n",
      "Sora原理与实战\n",
      "动手学习AI视频\n",
      "现有AI视频软件盘点\u000b\n",
      "随着2023年ChatGPT的爆发，让AI的普及率迅速提升，切实的让人感受到AI的给普通人带来的影响，在2023年火了一年的LLM后，视频领域也是也在2024年迅速崛起，前有前辈Runway，后有新生代产品pika，当然也有大名鼎鼎的开源救星SD的当家产品，StableVideoDiffusion，当这几家视频生成公司互相竞争，抢市场份\n"
     ]
    }
   ],
   "source": [
    "# 通过多个replace('\\n\\n', '\\n')将数据中多个连续的换行符减少至一个\n",
    "# replace(' ', '')将所有的空格去掉\n",
    "after_pro_data = ppt_data[0].page_content.replace('\\n\\n', '\\n').replace('\\n\\n', '\\n').replace('\\n\\n', '\\n').replace(' ', '')\n",
    "print(f'处理后的数据:\\n{after_pro_data[:200]}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.doc\\docx文件\n",
    "* 本部分示例文档将[《面向开发者的LLM入门教程、第一部分Prompt Engineering》md版本](https://github.com/datawhalechina/llm-cookbook)转为docx格式进行演示。  \n",
    "\n",
    "`langchain`中`UnstructuredWordDocumentLoader`可以读取doc与docx文件，且支持一次性读取全文与按元素读取两种模式。\n",
    "#### 2.1 一次性读取全文内容\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[Document(page_content='第一章 简介\\n\\n欢迎来到面向开发者的提示工程部分，本部分内容基于吴恩达老师的《Prompt Engineering for Developer》课程进行编写。《Prompt Engineering for Developer》课程是由吴恩达老师与 OpenAI 技术团队成员 Isa Fulford 老师合作授课，Isa 老师曾开发过受欢迎的 ChatGPT 检索插件，并且在教授 LLM （Large Language Model， 大语言模型）技术在产品中的应用方面做出了很大贡献。她还参与编写了教授人们使用 Prompt 的 OpenAI cookbook。我们希望通过本模块的学习，与大家分享使用提示词开发 LLM 应用的最佳实践和技巧。\\n\\n网络上有许多关于提示词（Prompt， 本教程中将保留该术语）设计的材料，例如《30 prompts everyone has to know》之类的文章，这些文章主要集中在 ChatGPT 的 Web 界面上，许多人在使用它执行特定的、通常是一次性的任务。但我们认为，对于开发人员，大语言模型（LLM） 的更强大功能是能通过 API 接口调用，从而快速构建软件应用程序。实际上，我们了解到 DeepLearning.AI 的姊妹公司 AI Fund 的团队一直在与许多初创公司合作，将这些技术应用于诸多应用程序上。很兴奋能看到 LLM API 能够让开发人员非常快速地构建应用程序。\\n\\n在本模块，我们将与读者分享提升大语言模型应用效果的各种技巧和最佳实践。书中内容涵盖广泛，包括软件开发提示词设计、文本总结、推理、转换、扩展以及构建聊天机器人等语言模型典型应用场景。我们衷心希望该课程能激发读者的想象力，开发出更出色的语言模型应用。\\n\\n随着 LLM 的发展，其大致可以分为两种类型，后续称为基础 LLM 和指令微调（Instruction Tuned）LLM。基础LLM是基于文本训练数据，训练出预测下一个单词能力的模型。其通常通过在互联网和其他来源的大量数据上训练，来确定紧接着出现的最可能的词。例如，如果你以“从前，有一只独角兽”作为 Prompt ，基础 LLM 可能会继续预测“她与独角兽朋友共同生活在一片神奇森林中”。但是，如果你以“法国的首都是什么”为 Prompt ，则基础 LLM 可能会根据互联网上的文章，将回答预测为“法国最大的城市是什么？法国的人口是多少？”，因为互联网上的文章很可能是有关法国国家的问答题目列表。\\n\\n与基础语言模型不同，指令微调 LLM 通过专门的训练，可以更好地理解并遵循指令。举个例子，当询问“法国的首都是什么？”时，这类模型很可能直接回答“法国的首都是巴黎”。指令微调 LLM 的训练通常基于预训练语言模型，先在大规模文本数据上进行预训练，掌握语言的基本规律。在此基础上进行进一步的训练与微调（finetune），输入是指令，输出是对这些指令的正确回复。有时还会采用RLHF（reinforcement learning from human feedback，人类反馈强化学习）技术，根据人类对模型输出的反馈进一步增强模型遵循指令的能力。通过这种受控的训练过程。指令微调 LLM 可以生成对指令高度敏感、更安全可靠的输出，较少无关和损害性内容。因此。许多实际应用已经转向使用这类大语言模型。\\n\\n因此，本课程将重点介绍针对指令微调 LLM 的最佳实践，我们也建议您将其用于大多数使用场景。当您使用指令微调 LLM 时，您可以类比为向另一个人提供指令（假设他很聪明但不知道您任务的具体细节）。因此，当 LLM 无法正常工作时，有时是因为指令不够清晰。例如，如果您想问“请为我写一些关于阿兰·图灵( Alan Turing )的东西”，在此基础上清楚表明您希望文本专注于他的科学工作、个人生活、历史角色或其他方面可能会更有帮助。另外您还可以指定回答的语调， 来更加满足您的需求，可选项包括专业记者写作，或者向朋友写的随笔等。\\n\\n如果你将 LLM 视为一名新毕业的大学生，要求他完成这个任务，你甚至可以提前指定他们应该阅读哪些文本片段来写关于阿兰·图灵的文本，这样能够帮助这位新毕业的大学生更好地完成这项任务。本书的下一章将详细阐释提示词设计的两个关键原则：清晰明确和给予充足思考时间。', metadata={'source': './data/1. 简介 Introduction.docx'})]\n"
     ]
    }
   ],
   "source": [
    "from langchain_community.document_loaders.word_document import UnstructuredWordDocumentLoader\n",
    "\n",
    "loader = UnstructuredWordDocumentLoader('./data/1. 简介 Introduction.docx', mode='single')\n",
    "docs = loader.load()\n",
    "print(f'全文内容：\\n{docs[0].page_content}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 2.2 按元素读取内容"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "第0个元素：\n",
      "第一章 简介\n",
      "第1个元素：\n",
      "欢迎来到面向开发者的提示工程部分，本部分内容基于吴恩达老师的《Prompt Engineering for Developer》课程进行编写。《Prompt Engineering for Developer》课程是由吴恩达老师与 OpenAI 技术团队成员 Isa Fulford 老师合作授课，Isa 老师曾开发过受欢迎的 ChatGPT 检索插件，并且在教授 LLM （Large Language Model， 大语言模型）技术在产品中的应用方面做出了很大贡献。她还参与编写了教授人们使用 Prompt 的 OpenAI cookbook。我们希望通过本模块的学习，与大家分享使用提示词开发 LLM 应用的最佳实践和技巧。\n",
      "第2个元素：\n",
      "网络上有许多关于提示词（Prompt， 本教程中将保留该术语）设计的材料，例如《30 prompts everyone has to know》之类的文章，这些文章主要集中在 ChatGPT 的 Web 界面上，许多人在使用它执行特定的、通常是一次性的任务。但我们认为，对于开发人员，大语言模型（LLM） 的更强大功能是能通过 API 接口调用，从而快速构建软件应用程序。实际上，我们了解到 DeepLearning.AI 的姊妹公司 AI Fund 的团队一直在与许多初创公司合作，将这些技术应用于诸多应用程序上。很兴奋能看到 LLM API 能够让开发人员非常快速地构建应用程序。\n",
      "第3个元素：\n",
      "在本模块，我们将与读者分享提升大语言模型应用效果的各种技巧和最佳实践。书中内容涵盖广泛，包括软件开发提示词设计、文本总结、推理、转换、扩展以及构建聊天机器人等语言模型典型应用场景。我们衷心希望该课程能激发读者的想象力，开发出更出色的语言模型应用。\n",
      "第4个元素：\n",
      "随着 LLM 的发展，其大致可以分为两种类型，后续称为基础 LLM 和指令微调（Instruction Tuned）LLM。基础LLM是基于文本训练数据，训练出预测下一个单词能力的模型。其通常通过在互联网和其他来源的大量数据上训练，来确定紧接着出现的最可能的词。例如，如果你以“从前，有一只独角兽”作为 Prompt ，基础 LLM 可能会继续预测“她与独角兽朋友共同生活在一片神奇森林中”。但是，如果你以“法国的首都是什么”为 Prompt ，则基础 LLM 可能会根据互联网上的文章，将回答预测为“法国最大的城市是什么？法国的人口是多少？”，因为互联网上的文章很可能是有关法国国家的问答题目列表。\n",
      "第5个元素：\n",
      "与基础语言模型不同，指令微调 LLM 通过专门的训练，可以更好地理解并遵循指令。举个例子，当询问“法国的首都是什么？”时，这类模型很可能直接回答“法国的首都是巴黎”。指令微调 LLM 的训练通常基于预训练语言模型，先在大规模文本数据上进行预训练，掌握语言的基本规律。在此基础上进行进一步的训练与微调（finetune），输入是指令，输出是对这些指令的正确回复。有时还会采用RLHF（reinforcement learning from human feedback，人类反馈强化学习）技术，根据人类对模型输出的反馈进一步增强模型遵循指令的能力。通过这种受控的训练过程。指令微调 LLM 可以生成对指令高度敏感、更安全可靠的输出，较少无关和损害性内容。因此。许多实际应用已经转向使用这类大语言模型。\n",
      "第6个元素：\n",
      "因此，本课程将重点介绍针对指令微调 LLM 的最佳实践，我们也建议您将其用于大多数使用场景。当您使用指令微调 LLM 时，您可以类比为向另一个人提供指令（假设他很聪明但不知道您任务的具体细节）。因此，当 LLM 无法正常工作时，有时是因为指令不够清晰。例如，如果您想问“请为我写一些关于阿兰·图灵( Alan Turing )的东西”，在此基础上清楚表明您希望文本专注于他的科学工作、个人生活、历史角色或其他方面可能会更有帮助。另外您还可以指定回答的语调， 来更加满足您的需求，可选项包括专业记者写作，或者向朋友写的随笔等。\n",
      "第7个元素：\n",
      "如果你将 LLM 视为一名新毕业的大学生，要求他完成这个任务，你甚至可以提前指定他们应该阅读哪些文本片段来写关于阿兰·图灵的文本，这样能够帮助这位新毕业的大学生更好地完成这项任务。本书的下一章将详细阐释提示词设计的两个关键原则：清晰明确和给予充足思考时间。\n"
     ]
    }
   ],
   "source": [
    "\n",
    "loader = UnstructuredWordDocumentLoader('./data/1. 简介 Introduction.docx', mode='elements')\n",
    "docs = loader.load()\n",
    "for i in range(len(docs)): print(f'第{i}个元素：\\n{docs[i].page_content}', end='\\n')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 三、数据清洗及脱敏\n",
    "## 1.数据清洗\n",
    "`langchain`的`ducoment_loaders`在读取数据时就已经将各种文档的特殊符号（比如markdown文件的`#、*`）去除，但有时文档内容还存在一些重复的字符以及无意义字符，需要我们进行数据清洗来保证向量模型能够准确提取语义以及给大语言模型参考的信息是方便理解且准确的。\n",
    "> emoji表情在计算机中是以Unicode编码方式存储的，如果想匹配emoji可以把emoji范围用Unicode编码表示出来再通过正则表达式去匹配。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "清洗前的句子：\n",
      "     llm_universe是一个面向小白开发者的大模型应用开发教程😍😍😍，\n",
      "\n",
      "旨在基于阿里云服务器😆😆😆，\n",
      "\n",
      "\n",
      "结合个人知识库助手项目，\n",
      "通过一个课程完成大模型开发的重点入门。\n",
      "\n",
      "\n",
      "学习llm_universe之后，\n",
      "我才知道大模型应用开发原来这么简单！🌟\n",
      "\n",
      "清洗后的句子：\n",
      "llm_universe是一个面向小白开发者的大模型应用开发教程，旨在基于阿里云服务器，结合个人知识库助手项目，通过一个课程完成大模型开发的重点入门。\n",
      "学习llm_universe之后，我才知道大模型应用开发原来这么简单！\n",
      "\n"
     ]
    }
   ],
   "source": [
    "import re\n",
    "\n",
    "def clean_text(text: str):\n",
    "    # 给出emoji范围并，删除emoji\n",
    "    emoji_pattern = re.compile(\"[\"\n",
    "                               u\"\\U0001F601-\\U0001F64F\"\n",
    "                               u\"\\U0001F300-\\U0001F5FF\"\n",
    "\n",
    "                                \"]+\", flags=re.UNICODE)\n",
    "    text = emoji_pattern.sub(r'', text)\n",
    "    # 删除字符串开头的空格\n",
    "    text = re.sub(r'^\\s+', '', text)\n",
    "    # 将多个回车改为一个\n",
    "    text = re.sub(r'\\n+', '\\n', text)\n",
    "    # 删除句子中（以逗号结尾）的回车\n",
    "    text = re.sub(r'，\\n', '，', text)\n",
    "\n",
    "    return text\n",
    "\n",
    "# 该字符串中存在重复的空格、换行符以及表情符号\n",
    "text = '''     llm_universe是一个面向小白开发者的大模型应用开发教程😍😍😍，\n",
    "\n",
    "旨在基于阿里云服务器😆😆😆，\n",
    "\n",
    "\n",
    "结合个人知识库助手项目，\n",
    "通过一个课程完成大模型开发的重点入门。\n",
    "\n",
    "\n",
    "学习llm_universe之后，\n",
    "我才知道大模型应用开发原来这么简单！🌟\n",
    "'''\n",
    "\n",
    "cleaned_text = clean_text(text)\n",
    "print(f'清洗前的句子：\\n{text}')\n",
    "print(f'清洗后的句子：\\n{cleaned_text}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2.数据脱敏\n",
    "若数据中含有电子邮件、网址等敏感信息，需要结合项目本身及应用判断是否需要进行数据脱敏。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "llm_universe的GitHub地址为\n",
      "Datawhale的GitHub地址为\n",
      "大模型实习生小明的邮箱为\n"
     ]
    }
   ],
   "source": [
    "text = '''llm_universe的GitHub地址为https://github.com/datawhalechina/llm-universe\n",
    "Datawhale的GitHub地址为https://github.com/datawhalechina\n",
    "大模型实习生小明的邮箱为xiaoming@gmail.com'''\n",
    "\n",
    "def remove_urls(text: str):\n",
    "    '''\n",
    "    http[s]?://匹配http://或https://，其中[s]?表示可选\n",
    "    ?:匹配后面的文本\n",
    "    [a-zA-Z]匹配大小写字母\n",
    "    [0-9]匹配数字\n",
    "    [$-_@.&+!*\\(\\),]匹配各种符号，其中在左右括号前边加了\\来转译\n",
    "    |意思为或，将所有条件并列起来\n",
    "    (?:%[0-9a-fA-F][0-9a-fA-F])匹配网址中URL编码比如%20，格式为%后加两位数字或字母\n",
    "    '''\n",
    "    url_pattern = r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+'\n",
    "    text = re.sub(url_pattern, '', text)\n",
    "\n",
    "    email_pattern = r'[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}'\n",
    "    text = re.sub(email_pattern, '', text)\n",
    "    return text\n",
    "text = remove_urls(text)\n",
    "print(text)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "llm_universe_2.x",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.14"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
